﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.IO;
using System.Xml;
using System.Windows.Forms;
using log4net;
using MPMovieCatalog.Domain;
using MPMovieCatalog.Util;

namespace MPMovieCatalog.Export
{
    public class HtmlExporter : Exporter
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(HtmlExporter));

        private static string TEMPLATES_PATH = Application.StartupPath + @"\templates\";

        private static string TEMPLATES_IMAGES_PATH = TEMPLATES_PATH + @"images";

        public HtmlExporter(ArrayList movies, HtmlListType listType, int moviesPerPage, string folderName, string indexFileName, bool overwriteFiles, string indexTitle, Template indexTemplate, Template detailsTemplate, int thumbSize, Dictionary<string, object> parameters)
            : base(ResourceUtils.GetString("htmlExport", 0), movies, folderName)
        {
            ListType = listType;
            MoviesPerPage = moviesPerPage;
            IndexFileName = indexFileName;
            OverwriteFiles = overwriteFiles;
            IndexTitle = indexTitle;
            IndexTemplate = indexTemplate;
            DetailsTemplate = detailsTemplate;
            ThumbSize = thumbSize;
            Parameters = parameters;
        }

        public int MoviesPerPage { get; set; }
        public HtmlListType ListType { get; set; }
        public string IndexFileName { get; set; }
        public bool OverwriteFiles { get; set; }
        public string IndexTitle { get; set; }
        public Template IndexTemplate { get; set; }
        public Template DetailsTemplate { get; set; }
        public int ThumbSize { get; set; }
        public Dictionary<string, object> Parameters { get; set; }

        protected override void Export()
        {
            OnProgress(0, ResourceUtils.GetString("htmlExport", 35));

            Directory.CreateDirectory(FileName);
            ExportImages();

            if (File.Exists(TEMPLATES_PATH + IndexTemplate.Name + ".css"))
            {
                File.Copy(TEMPLATES_PATH + IndexTemplate.Name + ".css", FileName + @"\" + IndexTemplate.Name + ".css", true);
            }

            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("pageTitle", IndexTitle);
            parameters.Add("details", DetailsTemplate != null);

            if (Parameters != null)
            {
                foreach (string key in Parameters.Keys)
                {
                    parameters.Add(key, Parameters[key]);
                }
            }

            OnProgress(0, ResourceUtils.GetString("htmlExport", 36));

            Dictionary<long, int> moviesPageNumber = new Dictionary<long, int>();
            if (ListType == HtmlListType.Single)
            {
                string moviesIndex = TemplateUtils.TransformMovies(Movies, IndexTemplate, false, false, parameters);

                StreamWriter sw = new StreamWriter(FileName + @"\" + IndexFileName);
                sw.Write(moviesIndex);
                sw.Close();

                OnProgress(100, ResourceUtils.GetString("htmlExport", 36));
            }
            else if (ListType == HtmlListType.Alphabetical)
            {
                SortedList<string, ArrayList> pages = new SortedList<string, ArrayList>();
                foreach (MovieInfo movieInfo in Movies)
                {
                    string firstLetter = movieInfo.Name.Substring(0, 1).ToUpper();
                    if (firstLetter.ToCharArray()[0] >= '0' && firstLetter.ToCharArray()[0] <= '9')
                    {
                        firstLetter = "0-9";
                    }

                    ArrayList array;
                    if (!pages.TryGetValue(firstLetter, out array))
                    {
                        array = new ArrayList();
                        pages.Add(firstLetter, array);
                    }
                    array.Add(movieInfo);
                }

                int pageCount = pages.Count;
                for (int pageNumber = 0; pageNumber < pageCount; pageNumber++)
                {
                    foreach (MovieInfo movieInfo in pages[pages.Keys[pageNumber]])
                    {
                        moviesPageNumber.Add(movieInfo.Id, pageNumber);
                    }
                    GenerateIndexPage(pages[pages.Keys[pageNumber]], pageNumber, pageCount, pages.Keys, parameters);
                }
            }
            else if (ListType == HtmlListType.Paged)
            {
                int pageCount = Movies.Count / MoviesPerPage + 1;

                for (int pageNumber = 0; pageNumber < pageCount; pageNumber++)
                {
                    int index = pageNumber * MoviesPerPage;
                    int count = Math.Min(index + MoviesPerPage, Movies.Count) - index;

                    foreach (MovieInfo movieInfo in Movies.GetRange(index, count))
                    {
                        moviesPageNumber.Add(movieInfo.Id, pageNumber);
                    }

                    GenerateIndexPage(Movies.GetRange(index, count), pageNumber, pageCount, null, parameters);
                }
            }

            ExportDetails(moviesPageNumber);
        }

        private void ExportImages()
        {
            Image.GetThumbnailImageAbort callback = new Image.GetThumbnailImageAbort(ThumbnailCallback);

            string imagesFolder = string.Format(@"{0}\images", FileName);
            Directory.CreateDirectory(imagesFolder);

            int itemNumber = 0;
            foreach (MovieInfo movieInfo in Movies)
            {
                int percent = ++itemNumber / Movies.Count * 100;
                OnProgress(percent, string.Format(ResourceUtils.GetString("htmlExport", 37), itemNumber, Movies.Count));

                if (movieInfo.Picture != null && movieInfo.Picture != string.Empty)
                {
                    movieInfo.PictureFileName = imagesFolder + @"\" + movieInfo.Id + ".jpg";
                    movieInfo.Thumb = imagesFolder + @"\" + movieInfo.Id + "t.jpg";
                    if (!File.Exists(movieInfo.PictureFileName) || (File.Exists(movieInfo.PictureFileName) && OverwriteFiles))
                    {
                        MemoryStream stream = new MemoryStream(Convert.FromBase64String(movieInfo.Picture));
                        Bitmap image = new Bitmap(stream);
                        image.Save(movieInfo.PictureFileName, ImageFormat.Jpeg);

                        if (ThumbSize > 0)
                        {
                            Image thumb = image.GetThumbnailImage(ThumbSize, ThumbSize * image.Height / image.Width, callback, IntPtr.Zero);
                            thumb.Save(movieInfo.Thumb, ImageFormat.Jpeg);
                        }
                    }
                }
            }

            OnProgress(0, ResourceUtils.GetString("htmlExport", 38));

            CopyDirectory(TEMPLATES_IMAGES_PATH, FileName);

            OnProgress(100, ResourceUtils.GetString("htmlExport", 38));
        }

        public bool ThumbnailCallback()
        {
            return false;
        }

        private void ExportDetails(Dictionary<long, int> moviesPageNumber)
        {
            if (DetailsTemplate != null)
            {
                string detailsFolder = string.Format(@"{0}\details", FileName);
                Directory.CreateDirectory(detailsFolder);

                if (File.Exists(Application.StartupPath + @"\templates\" + DetailsTemplate.Name + ".css"))
                {
                    File.Copy(Application.StartupPath + @"\templates\" + DetailsTemplate.Name + ".css", detailsFolder + @"\" + DetailsTemplate.Name + ".css", true);
                }

                for (int i = 0; i < Movies.Count; i++)
                {
                    int percent = (i + 1) / Movies.Count * 100;
                    OnProgress(percent, string.Format(ResourceUtils.GetString("htmlExport", 39), (i + 1), Movies.Count));

                    MovieInfo movieInfo = Movies[i] as MovieInfo;
                    string detailsFileName = detailsFolder + @"\" + movieInfo.Id + ".html";
                    if (!File.Exists(detailsFileName) || (File.Exists(detailsFileName) && OverwriteFiles))
                    {
                        XmlDocument movieXml = movieInfo.ToXML();
                        XmlElement navigationXml = movieXml.CreateElement("navigation");
                        movieXml.DocumentElement.AppendChild(navigationXml);

                        // firstlink
                        XmlElement element = movieXml.CreateElement("firstlink");
                        navigationXml.AppendChild(element);
                        XmlAttribute urlAttr = movieXml.CreateAttribute("url");
                        element.Attributes.Append(urlAttr);
                        if (i > 0)
                        {
                            urlAttr.Value = ((MovieInfo)Movies[0]).Id + ".html";
                        }

                        // lastlink
                        element = movieXml.CreateElement("lastlink");
                        navigationXml.AppendChild(element);
                        urlAttr = movieXml.CreateAttribute("url");
                        element.Attributes.Append(urlAttr);
                        if (i < Movies.Count - 1)
                        {
                            urlAttr.Value = ((MovieInfo)Movies[Movies.Count - 1]).Id + ".html";
                        }

                        // prevlink
                        element = movieXml.CreateElement("prevlink");
                        navigationXml.AppendChild(element);
                        urlAttr = movieXml.CreateAttribute("url");
                        element.Attributes.Append(urlAttr);

                        if (i > 0)
                        {
                            urlAttr.Value = ((MovieInfo)Movies[i - 1]).Id + ".html";
                        }

                        // nextlink
                        element = movieXml.CreateElement("nextlink");
                        navigationXml.AppendChild(element);
                        urlAttr = movieXml.CreateAttribute("url");
                        element.Attributes.Append(urlAttr);

                        if (i < Movies.Count - 1)
                        {
                            urlAttr.Value = ((MovieInfo)Movies[i + 1]).Id + ".html";
                        }

                        // uplink

                        int pageNumber = 0;
                        if (moviesPageNumber != null && moviesPageNumber.ContainsKey(movieInfo.Id))
                        {
                            pageNumber = moviesPageNumber[movieInfo.Id];
                        }

                        string indexFileName = Path.GetFileNameWithoutExtension(IndexFileName) + (pageNumber > 0 ? pageNumber.ToString() : "") + ".html";

                        element = movieXml.CreateElement("uplink");
                        navigationXml.AppendChild(element);
                        urlAttr = movieXml.CreateAttribute("url");
                        element.Attributes.Append(urlAttr);
                        urlAttr.Value = "../" + indexFileName;

                        string movieDetails = TemplateUtils.TransformMovie(movieXml, DetailsTemplate, false, null);

                        StreamWriter sw = new StreamWriter(detailsFileName);
                        sw.Write(movieDetails);
                        sw.Close();
                    }
                }
            }
        }

        private void GenerateIndexPage(ArrayList pageMovies, int pageNumber, int pageCount, IList<string> pageNumbers, Dictionary<string, object> parameters)
        {
            int percent = (pageNumber + 1) / pageCount * 100;
            OnProgress(percent, string.Format(ResourceUtils.GetString("htmlExport", 40), (pageNumber + 1), pageCount));

            XmlDocument moviesXml = new XmlDocument();
            XmlElement root = moviesXml.CreateElement("movies");
            moviesXml.AppendChild(root);

            foreach (MovieInfo movieInfo in pageMovies)
            {
                XmlElement movieInfoXml = moviesXml.CreateElement("movieInfo");
                root.AppendChild(movieInfoXml);

                movieInfo.ToXML(movieInfoXml);
            }

            GenerateIndexPageNavigation(moviesXml, pageNumber, pageCount, pageNumbers);

            string moviesIndex = TemplateUtils.TransformMovies(moviesXml, IndexTemplate, false, parameters);

            string pageIndexFileName = Path.GetFileNameWithoutExtension(IndexFileName) + (pageNumber > 0 ? pageNumber.ToString() : "") + ".html";
            StreamWriter sw = new StreamWriter(FileName + @"\" + pageIndexFileName);
            sw.Write(moviesIndex);
            sw.Close();
        }

        private void GenerateIndexPageNavigation(XmlDocument moviesXml, int pageNumber, int pageCount, IList<string> pageNumbers)
        {
            string indexFileNameWithoutExtension = Path.GetFileNameWithoutExtension(IndexFileName);

            XmlElement root = moviesXml.DocumentElement;
            XmlElement navigationXml = moviesXml.CreateElement("navigation");
            root.AppendChild(navigationXml);

            for (int j = 0; j < pageCount; j++)
            {
                string pageNumberStr = (pageNumbers != null && pageNumbers.Count > j) ? pageNumbers[j] : (j + 1).ToString();

                XmlElement pagelinkXml = moviesXml.CreateElement("pagelink");
                navigationXml.AppendChild(pagelinkXml);

                XmlAttribute urlAttr = moviesXml.CreateAttribute("url");
                pagelinkXml.Attributes.Append(urlAttr);
                if (j != pageNumber)
                {
                    urlAttr.Value = indexFileNameWithoutExtension + (j > 0 ? j.ToString() : "") + ".html";
                }

                XmlAttribute pagenumAttr = moviesXml.CreateAttribute("pagenum");
                pagelinkXml.Attributes.Append(pagenumAttr);
                pagenumAttr.Value = pageNumberStr;
            }

            // firstlink
            XmlElement linkXml = moviesXml.CreateElement("firstlink");
            navigationXml.AppendChild(linkXml);
            XmlAttribute linkUrlAttr = moviesXml.CreateAttribute("url");
            linkXml.Attributes.Append(linkUrlAttr);

            if (pageNumber != 0)
            {
                linkUrlAttr.Value = indexFileNameWithoutExtension + ".html";
            }

            // lastlink
            linkXml = moviesXml.CreateElement("lastlink");
            navigationXml.AppendChild(linkXml);
            linkUrlAttr = moviesXml.CreateAttribute("url");
            linkXml.Attributes.Append(linkUrlAttr);

            if (pageNumber < pageCount - 1)
            {
                linkUrlAttr.Value = indexFileNameWithoutExtension + (pageCount - 1) + ".html";
            }

            // prevlink
            linkXml = moviesXml.CreateElement("prevlink");
            navigationXml.AppendChild(linkXml);
            linkUrlAttr = moviesXml.CreateAttribute("url");
            linkXml.Attributes.Append(linkUrlAttr);

            if (pageNumber > 0)
            {
                int num = pageNumber - 1;
                linkUrlAttr.Value = indexFileNameWithoutExtension + (num > 0 ? num.ToString() : "") + ".html";
            }

            // nextlink
            linkXml = moviesXml.CreateElement("nextlink");
            navigationXml.AppendChild(linkXml);
            linkUrlAttr = moviesXml.CreateAttribute("url");
            linkXml.Attributes.Append(linkUrlAttr);

            if (pageNumber < pageCount - 1)
            {
                int num = pageNumber + 1;
                linkUrlAttr.Value = indexFileNameWithoutExtension + num + ".html";
            }
        }

        private void CopyDirectory(string sourceDir, string destDir)
        {
            try
            {
                DirectoryInfo root = new DirectoryInfo(sourceDir);
                string destSubDir = destDir + @"\" + root.Name;
                Directory.CreateDirectory(destSubDir);
                foreach (string file in Directory.GetFiles(sourceDir))
                {
                    File.Copy(file, destSubDir + @"\" + Path.GetFileName(file), true);
                }

                DirectoryInfo[] subDirs = root.GetDirectories();
                foreach (DirectoryInfo dirInfo in subDirs)
                {
                    CopyDirectory(dirInfo.FullName, destSubDir);
                }
            }
            catch (System.Exception ex)
            {
                log.Error(string.Format("Error coping directory: {0} to: {1}, message: {2}", sourceDir, destDir, ex.Message), ex);
            }

        }
    }

    public enum HtmlListType
    {
        Single = 0,
        Alphabetical = 1,
        Paged = 2
    }
}
